﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Linq.Expressions;

namespace Sio.Mdm.BL.Base
{
    public class SioMdmFilterFormatter : IQToolkit.ExpressionVisitor
    {
        private List<string> _where = new List<string>();
        private Dictionary<string, string> _leftJoin = new Dictionary<string, string>();
        private Dictionary<string, string> _leftJoinAdditionalCondition = new Dictionary<string, string>();
        private List<string> _orderBy = new List<string>();
        private int _take = -1;
        private int _skip = -1;
        private Dictionary<string, Object> _parameters = new Dictionary<string, object>();
        private List<Sio.Mdm.Linq.TableAlias> _aliases;
        //private Dictionary<string, Sio.Mdm.Entities.EntityFactory.EntityInfo> _aliases = new Dictionary<string, Sio.Mdm.Entities.EntityFactory.EntityInfo>();

        private SioMdmFilterFormatter()
        {
        }

        public static string RealTableName(Sio.Mdm.Linq.TableAlias alias, Sio.Mdm.Entities.EntityFactory.EntityInfo info)
        {
            //preverim če ima atribut stored procedura, če ga ima naredim format drugače vrnem table name
            foreach (var atr in info.Attributes)
            {
                if (atr.GetType() == typeof(Sio.Mdm.Entities.StoredProcedureEntityAttribute))
                {
                    var result = String.Format(((Sio.Mdm.Entities.StoredProcedureEntityAttribute)atr).StoredProcedureName, alias.Params);
                    return result;
                }
            }
            return info.TableName;
        }

        public static QueryCommand Format(Expression exp, bool deletedRecords)
        {
            var rep = new SioMdmFilterFormatter();

            rep._aliases = Sio.Mdm.Linq.FilterNominator.Nominate(exp);
            rep.Visit(exp);

            var sb = new StringBuilder();
            sb.Append("SELECT ");
            if (rep._take > -1)
              sb.AppendFormat("\n   FIRST {0} ", rep._take);

            if (rep._skip > -1)
                sb.AppendFormat("\n  SKIP {0} ", rep._skip);

            string mainAlias = "A0";
            if (rep._aliases.Count > 0)
            {
                MethodCallExpression mc = exp as MethodCallExpression;
                if ((mc != null) && (mc.Type.GetGenericArguments().Count() == 1 ))
                {
                    var alias = rep._aliases.Where(r => r.EntityType == mc.Type.GetGenericArguments()[0]).First();
                    if (alias != null)
                        mainAlias = alias.LocalName;
                }
            }
            sb.AppendFormat("\n   {0}.*", mainAlias);  

            var aliases = new Dictionary<string, Sio.Mdm.Entities.EntityFactory.EntityInfo>();
            var aliasesFull = new Dictionary<string, string>();
            
            foreach (var alias in rep._aliases)
            {
                var info = Sio.Mdm.Entities.EntityFactory.GetEntityInfo(alias.EntityType);
                aliases.Add(alias.LocalName, info);
                aliasesFull.Add(alias.LocalName, RealTableName(alias, info));

                //za vsako tabelo preverim še če ima status field in če ga ima ga ustrezno dodam v where pogoj
                if (info.DeletedStatusField != null)
                {
                    if (rep._leftJoin.ContainsKey(alias.LocalName))
                    {
                        if (deletedRecords)
                            rep._leftJoinAdditionalCondition.Add(alias.LocalName, String.Format("{0}.{1} = 1", alias.LocalName, info.DeletedStatusField.Name));
                        else
                            rep._leftJoinAdditionalCondition.Add(alias.LocalName, String.Format("(({0}.{1} <> 1) or ({0}.{1} is null))", alias.LocalName, info.DeletedStatusField.Name));
                    }
                    else
                    {
                        if (deletedRecords)
                            rep._where.Add(String.Format("{0}.{1} = 1", alias.LocalName, info.DeletedStatusField.Name));
                        else
                            rep._where.Add(String.Format("(({0}.{1} <> 1) or ({0}.{1} is null))", alias.LocalName, info.DeletedStatusField.Name));
                    }
                }
            }


            //naštejem vse tabele, izločim tabele nad katerimi je left-outer join
            //var from = aliasesFull.Select(r => String.Format("{0} {1}", r.Value, r.Key)).ToArray();

            var fromWithoutLeftJoin = new Dictionary<string, string>();
            foreach (var tbl in aliasesFull)
            {
                if (!rep._leftJoin.ContainsKey(tbl.Key))
                    fromWithoutLeftJoin.Add(tbl.Key, tbl.Value);
            }
            var from = fromWithoutLeftJoin.Select(r => String.Format("{0} {1}", r.Value, r.Key)).ToArray();
            string fromTables = String.Join(", ", from);


            sb.AppendFormat("\nFROM \n   {0} ", fromTables);

            if (rep._leftJoin.Count() > 0)
            {
                foreach (var left in rep._leftJoin)
                {
                    var tbName = aliasesFull[left.Key];

                    if (rep._leftJoinAdditionalCondition.ContainsKey(left.Key))
                        sb.AppendFormat("\n LEFT OUTER JOIN {0} {1} ON ({2} and {3})", tbName, left.Key, left.Value, rep._leftJoinAdditionalCondition[left.Key]);
                    else
                        sb.AppendFormat("\n LEFT OUTER JOIN {0} {1} ON {2}", tbName, left.Key, left.Value);
                }
            }

            if (rep._where.Count() > 0)
            {
                sb.Append("\nWHERE \n   ");
                sb.Append(String.Join(" \n   AND ", rep._where.ToArray()));
            }



            if ((rep._orderBy.Count() > 0))
            {
                sb.Append("\nORDER BY \n   ");
                sb.Append(String.Join(", ", rep._orderBy.ToArray()));
            }

            var result = new QueryCommand(sb.ToString(), rep._parameters);
            return result;
        }

        private string FormatFieldExpression(Expression exp)
        {
            return IQToolkit.Data.Firebird.FirebirdFormatter.FormatFields(((LambdaExpression)((UnaryExpression)exp).Operand).Body, ref _parameters, _aliases);
        }

        private string WriteIEnumerableList(object list)
        {
            List<string> values = new List<string>();
            System.Collections.IEnumerable valuesList= 
                list as System.Collections.IEnumerable;

            if (valuesList != null)
            {
                foreach (var val in valuesList)
                {
                    values.Add(String.Format("'{0}'", val.ToString()));
                }
            }

            return String.Join(", ", values.ToArray());
        }

        //tumasta metoda ki iz sql stringa poišče alias. Potrebno narediti boljšo...
        private string FindTableAlias(string sql)
        {
            for (var i = 0; i < 20; i++)
            {
                var alias = String.Format("A{0}.", i);
                if (sql.Contains(alias))
                    return String.Format("A{0}", i);
            }
            return null;
        }

        protected override Expression VisitMethodCall(MethodCallExpression m)
        {
            if ((m.Method.DeclaringType == typeof(Sio.Mdm.Entities.FilterMethods)))
            {
                switch (m.Method.Name)
                {
                    case "Take":
                        this.Visit(m.Arguments[0]);
                        _take = int.Parse(((ConstantExpression)m.Arguments[1]).Value.ToString());
                        return m;
                    case "Skip":
                        this.Visit(m.Arguments[0]);
                        _skip = int.Parse(((ConstantExpression)m.Arguments[1]).Value.ToString());
                        return m;
                    case "OrderBy":
                        this.Visit(m.Arguments[0]);
                        _orderBy.Add(FormatFieldExpression(m.Arguments[1]));
                        return m;
                    case "OrderByDescending":
                        this.Visit(m.Arguments[0]);
                        _orderBy.Add(String.Format("{0} desc", FormatFieldExpression(m.Arguments[1])));
                        return m;
                    case "Join":
                        this.Visit(m.Arguments[0]);
                        this.Visit(m.Arguments[1]);
                        _where.Add(String.Format("({0} = {1})", FormatFieldExpression(m.Arguments[2]), FormatFieldExpression(m.Arguments[3])));
                        return m;
                    case "LeftJoin":
                        this.Visit(m.Arguments[0]);
                        this.Visit(m.Arguments[1]);
                        var left = FormatFieldExpression(m.Arguments[2]);
                        var rigth = FormatFieldExpression(m.Arguments[3]);
                        _leftJoin.Add(FindTableAlias(rigth), String.Format("({0} = {1})", left, rigth));
                        _where.Add(FormatFieldExpression(m.Arguments[5]));
                        return m;
                    case "Where":
                        this.Visit(m.Arguments[0]);
                        _where.Add(FormatFieldExpression(m.Arguments[1]));
                        return m;
                    case "Select":
                        this.Visit(m.Arguments[0]);
                        return m;
                    case "SelectByField":
                        this.Visit(m.Arguments[0]);
                        var field = FormatFieldExpression(m.Arguments[1]);
                        var values = WriteIEnumerableList(((ConstantExpression)m.Arguments[2]).Value);
                        _where.Add(String.Format("({0} in ({1}))", field, values));
                        return m;
                    default:
                        return m;
                }
            }
            else return m;
        }
    }
}
